package gui.table;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import models.DatabaseModel;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

/**
 * Table widget for displaying a List<DatabaseModel>. It wraps around the List and 
 * can be updated when the list is changed.
 * 
 * The data in the <code>DatabaseModel</code>s is extracted by using a <code>TableView</code>.
 * The list is filtered by a {@link ListTransformer} object, to allow for e.g. searching.
 */
public class AutoTable < Model extends DatabaseModel > {
	
	private Table table;
	private TableView<Model> tableView;
	private ListTransformer<Model> transformer;
	private List<Model> list = new ArrayList<Model>();
	private List<Model> displayedList = new ArrayList<Model>();
	// Columns only for convenient looping 
	private int columns;
	private int sortColumn = -1;
	private boolean sortAscending = true;

	/**
	 * Create a new table, which will be capable of displaying a databaseList.
	 * 
	 * @param parent parent composite
	 * @param tableView {@link TableView} object for mapping the list items to a string representation. 
	 * @param transformer {@link ListTransformer} object for fintering the list.   
	 */
	public AutoTable(Composite parent, TableView<Model> tableView, ListTransformer<Model> transformer) {
		this.tableView = tableView;
		this.transformer = transformer;
		
		table = new Table (parent, SWT.BORDER | SWT.FULL_SELECTION | SWT.V_SCROLL);
		table.setLinesVisible (true);
		table.setHeaderVisible (true);
		
		String[] titles = tableView.getHeaders();
		columns = titles.length;
		for (int i=0; i<columns; i++) {
			TableColumn column = new TableColumn(table, SWT.NONE);
			column.setText(titles[i]);
			final int thisColumnIndex = i;
			// Detect sort command
			column.addListener(SWT.Selection, new Listener() {
				// Activated when user clicks column header
				@Override
				public void handleEvent(Event event) {
					sortEvent(thisColumnIndex);
				}
			});
		}	
	}
	
	/**
	 * Set the list to be displayed. (GUI will be updated.)
	 * @param list
	 */
	public void setList(List<Model> list) {
		this.list = list;
		update();
	}

	/** 
	 * Trigger to sort the table. Triggering several times reverses the sort dicection.
	 * @param thisColumnIndex
	 */
	private void sortEvent(int thisColumnIndex) {
		if (sortColumn != thisColumnIndex) {
			sortColumn = thisColumnIndex;
			sortAscending = true;
		}
		else {
			sortAscending = ! sortAscending;
		}
		update();
	}
	
	/**
	 * Force a refresh of the contents in the table.
	 */
	public void update() {
		Model selected = getSelected();
		displayedList = sort(transformer.transform(list));
		
		table.removeAll();
		for (Model model : displayedList) {
			TableItem item = new TableItem(table, SWT.NONE);
			String[] fields = tableView.getFields(model);
			for (int i=0; i<columns; i++) {
				item.setText(i, fields[i]);
			}
		}
		if (displayedList.contains(selected)) {
			select(selected);
		}
	}	
	
	/**
	 * Set the width of each column such that the contents are visible.
	 */
	public void pack() {
		for (int i=0; i<columns; i++) {
			table.getColumn(i).pack();
		}	
	}
	
	/**
	 * Sort the list of items to display.
	 * 
	 * @param content: list to sort 
	 * @return sorted list
	 */
	private List<Model> sort(List<Model> content){
		if (sortColumn == -1) {
			return content;
		}
		final int sortCollumn = this.sortColumn;
		final boolean sortAscending = this.sortAscending;
		Collections.sort(content, new Comparator<Model>(){
			@Override
			public int compare(Model m1, Model m2) {
				String str1 = tableView.getFields(m1)[sortCollumn];
				String str2 = tableView.getFields(m2)[sortCollumn];
				int compared = str1.compareToIgnoreCase(str2);
				return sortAscending? compared : -compared; 
			}
		});
		return content;
	}

	/** Delegates call to the table */
	public void setLayoutData(Object layoutData) {
		table.setLayoutData(layoutData);
	}
	
	/** Delegates call to the table */
	public void addListener(int eventType, Listener listener) {
		table.addListener(eventType, listener);
	}

	/**
	 * Get the selected Model/entry, or <code>null</code> if none is selected.
	 * @return Selected Model or <code>null</code>
	 */
	public Model getSelected() {
		int selectedIndex = table.getSelectionIndex();
		// If noting is selected, selected is -1
		if (selectedIndex != -1) {
			return displayedList.get(selectedIndex);
		}
		return null;
	}

	/**
	 * Select a specified client in the table.
	 * @param client
	 */
	public void select(Model client) {
		table.select(displayedList.indexOf(client));
	}

	/**
	 * Set whether the list selection can be edited.
	 * @param editable
	 */
	public void setEditable(boolean editable) {
		table.setEnabled(editable);
	}

}
